﻿using System.IO;
using Microsoft.Xna.Framework.Graphics;

namespace Ultima.Xna.Data
{
    public class Art
    {
        private static Texture2D[] _cache = new Texture2D[0x10000];
        private static FileIndex _indexFile = new FileIndex("Artidx.mul", "Art.mul", 0x10000, 4);
        private static ushort[][] _staticDimensions = new ushort[0x4000][];

        public static Texture2D GetLandTexture(int index, GraphicsDevice graphicsDevice)
        {
            index &= 0x3FFF;

            Texture2D data = _cache[index];

            if (data == null)
            {
                _cache[index] = data = ReadLandTexture(index, graphicsDevice);
            }

            return data;
        }

        public static void GetStaticDimensions(int index, out int width, out int height)
        {
            index &= 0x3FFF;

            ushort[] dimensions = _staticDimensions[index];

            if (dimensions == null)
            {
                _staticDimensions[index] = dimensions = ReadStaticDimensions(index + 0x4000);
            }

            width = dimensions[0];
            height = dimensions[1];
        }

        public static Texture2D GetStaticTexture(int index, GraphicsDevice graphicsDevice)
        {
            index &= 0x3FFF;
            index += 0x4000;

            Texture2D data = _cache[index];

            if (data == null)
            {
                _cache[index] = data = ReadStaticTexture(index, graphicsDevice);
            }

            return data;
        }

        private static unsafe Texture2D ReadLandTexture(int index, GraphicsDevice graphicsDevice)
        {
            _indexFile.Seek(index);

            ushort[] data = new ushort[44 * 44];

            int count = 2;
            int offset = 21;

            using (BinaryReader reader = new BinaryReader(_indexFile.Stream))
            {
                fixed (ushort* pData = data)
                {
                    ushort* dataRef = pData;

                    for (int y = 0; y < 22; y++, count += 2, offset--, dataRef += 44)
                    {
                        ushort* start = dataRef + offset;
                        ushort* end = start + count;

                        while (start < end)
                        {
                            *start++ = (ushort)(reader.ReadUInt16() ^ 0x8000);
                        }
                    }

                    count = 44;
                    offset = 0;

                    for (int y = 0; y < 22; y++, count -= 2, offset++, dataRef += 44)
                    {
                        ushort* start = dataRef + offset;
                        ushort* end = start + count;

                        while (start < end)
                        {
                            *start++ = (ushort)(reader.ReadUInt16() ^ 0x8000);
                        }
                    }
                }
            }

            Texture2D texture = new Texture2D(graphicsDevice, 44, 44, 1, TextureUsage.None, SurfaceFormat.Bgra5551);

            texture.SetData<ushort>(data);

            return texture;
        }

        private static ushort[] ReadStaticDimensions(int index)
        {
            _indexFile.Seek(index);

            ushort[] dimensions;

            using (BinaryReader reader = new BinaryReader(_indexFile.Stream))
            {
                reader.ReadInt32();

                dimensions = new ushort[] { (ushort)reader.ReadInt16(), (ushort)reader.ReadInt16() };
            }

            return dimensions;
        }

        private static unsafe Texture2D ReadStaticTexture(int index, GraphicsDevice graphicsDevice)
        {
            _indexFile.Seek(index);

            int width;
            int height;
            ushort[] data;

            using (BinaryReader reader = new BinaryReader(_indexFile.Stream))
            {
                reader.ReadInt32();

                width = reader.ReadInt16();
                height = reader.ReadInt16();

                if (width <= 0 || height <= 0)
                {
                    return null;
                }

                if (_staticDimensions[index - 0x4000] == null)
                {
                    _staticDimensions[index - 0x4000] = new ushort[] { (ushort)width, (ushort)height };
                }

                int[] lookups = new int[height];

                int dataStart = (int)reader.BaseStream.Position + (height * 2);

                for (int i = 0; i < height; i++)
                {
                    lookups[i] = (int)(dataStart + (reader.ReadUInt16() * 2));
                }

                data = new ushort[width * height];

                fixed (ushort* pData = data)
                {
                    ushort* dataRef = pData;

                    for (int y = 0; y < height; y++, dataRef += width)
                    {
                        reader.BaseStream.Seek(lookups[y], SeekOrigin.Begin);

                        ushort* start = dataRef;

                        int count, offset;

                        while (((offset = reader.ReadUInt16()) + (count = reader.ReadUInt16())) != 0)
                        {
                            start += offset;
                            ushort* end = start + count;

                            while (start < end)
                            {
                                *start++ = (ushort)(reader.ReadUInt16() ^ 0x8000);
                            }
                        }
                    }
                }
            }

            Texture2D texture = new Texture2D(graphicsDevice, width, height, 1, TextureUsage.None, SurfaceFormat.Bgra5551);

            texture.SetData<ushort>(data);

            return texture;
        }
    }
}